package com.liuwei.spring.cloud.biz.support;/**
 * @author : GlobalExceptionUtils
 * @version V1.0
 * @date: 2023/3/1 11:02 上午
 * @Description: TODO
 */

import cn.hutool.core.util.StrUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.liuwei.spring.cloud.common.utils.DateHelper;
import com.liuwei.spring.cloud.common.utils.Jackson;
import com.liuwei.spring.cloud.framework.spring.ApplicationHelper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * @author : GlobalExceptionUtils
 * @version V1.0
 * @date: 2023/3/1 11:02 上午
 * @Description: TODO
 */
@Slf4j
public class GlobalExceptionUtils {

    /**
     * 统一处理全局异常信息
     * @param e
     */
    public static void handleGlobalException(Throwable e) {
        // 全局异常处理
        ApolloValues apollo = ApplicationHelper.getBean(ApolloValues.class);
        String webhook = apollo.getSystem_exception_warning_webhook();
        List<String> ignoreClass = new ArrayList<>();
        if(StringUtils.isNotEmpty(apollo.getSystem_exception_warning_ignore_class())){
            ignoreClass = Jackson.parseArray(apollo.getSystem_exception_warning_ignore_class(),String.class);
        }
        List<String> ignoreWords = new ArrayList<>();
        if(StringUtils.isNotEmpty(apollo.getSystem_exception_warning_ignore_words())){
            ignoreWords = Jackson.parseArray(apollo.getSystem_exception_warning_ignore_words(),String.class);
        }
        List<ExceptionWebhook> expression = new ArrayList<>();
        if(StringUtils.isNotEmpty(apollo.getSystem_exception_warning_expression())){
            expression = Jackson.parseArray(apollo.getSystem_exception_warning_expression(),ExceptionWebhook.class);
        }

        String title = StrUtil.format("SpringCloud 系统运行异常");
        ExceptionContext context = buildMessages(title,e);

        // 全局异常处理; 区分黑名单机制
        boolean ignoreNotice = false;
        if(!ignoreNotice){
            ignoreNotice = ObjectUtils.isNotEmpty(CollectionUtils.emptyIfNull(ignoreClass).stream().filter(x->{
                return StringUtils.containsIgnoreCase(e.getClass().getSimpleName(),x);
            }).findFirst().orElse(null));
        }
        if(!ignoreNotice){
            ignoreNotice = ObjectUtils.isNotEmpty(CollectionUtils.emptyIfNull(ignoreWords).stream().filter(x->{
                return StringUtils.containsIgnoreCase(e.getClass().getSimpleName(),x);
            }).findFirst().orElse(null));
        }
        if(!ignoreNotice){
            CompletableFuture.runAsync(new Runnable() {
                @Override
                public void run() {
                    try {
                        notice(webhook,context);
                    } catch (Exception exception) {
                        log.error("系统全局异常信息发送失败",exception);
                    }
                }
            });
        }

        // 轮训所有消息配置
        if(CollectionUtils.isNotEmpty(expression)){
            Map<String,Object> properties = new HashMap<>();
            Field[] fields = context.getClass().getDeclaredFields();
            for (Field field : fields) {
                try {
                    // 设置访问权限，因为private修饰的字段默认不可直接访问
                    field.setAccessible(true);
                    String key = field.getName();
                    Object value = null;
                    value = field.get(context);
                    properties.put(key, value);
                } catch (IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
            for (int i = 0; i < expression.size(); i++) {
                ExceptionWebhook config = expression.get(i);
                String exp = config.getExpression();
                try {
                    Boolean needWarning = (Boolean) AviatorEvaluator.execute(exp, properties);
                    if(needWarning && StringUtils.isNotEmpty(config.getWebhook())){
                        String hook = config.getWebhook();
                        notice(hook,context);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    private static void notice(String webhook,ExceptionContext context) throws Exception {
        // 发送飞书信息
        List<Pair<String,String>> pairs = new ArrayList<>();
        pairs.add(Pair.of("应用名称: ",context.getApplication()));
        pairs.add(Pair.of("运行集群: ",context.getCluster()));
        pairs.add(Pair.of("环境信息: ",context.getProfile()));
        pairs.add(Pair.of("发生时间: ",DateHelper.formatStandard24Second(context.getTime())));
        pairs.add(Pair.of("日志追踪: ",context.getTraceId()));
        pairs.add(Pair.of("异常类型: ",context.getClazz()));
        pairs.add(Pair.of("错误信息: ",context.getMessage()));
        pairs.add(Pair.of("错误详情: ",context.getStacks()));
        // 发送信息
        //FeishuHelper.Webhook.sendTextMessage(webhook,context.getTitle(),pairs);
    }


    private static ExceptionContext buildMessages(String title,Throwable e){
        ExceptionContext context = new ExceptionContext();
        context.setApplication(ApplicationHelper.getApplicationName());
        context.setCluster(ApplicationHelper.getApplicationCluster());
        context.setProfile(ApplicationHelper.getProfileActive());

        context.setTitle(title);
        String traceId = TraceHelper.Micrometer.traceId();
        context.setTraceId(traceId);

        // 堆栈信息
        context.setClazz(e.getClass().getSimpleName());
        context.setMessage(e.getMessage());

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        e.printStackTrace(new PrintStream(baos));
        String stacks = StringUtils.left(baos.toString(),800);
        context.setStacks(stacks);

        context.setTime(new Date());
        return context;
    }

    @Data
    public static class ExceptionContext{
        String application;
        String cluster;
        String profile;
        String title;
        String traceId;
        String clazz;
        String message;
        String stacks;
        Date time;
    }

    @Data
    public static class ExceptionWebhook{
        String name;
        String expression;
        String webhook;
    }
}
